package com.neural.infrastructure;

import java.util.ArrayList;
import java.util.List;

import com.neural.descriptor.LayerDescriptor;
import com.neural.descriptor.Point;
import com.neural.metric.Metric;
import com.neural.metric.MetricManager;
import com.neural.neighbourhood.Neighbourhood;
import com.neural.neighbourhood.NeighbourhoodManager;

public class KohonenLayer extends Layer {

	private final Metric metricMethod;
	private final Integer neighbourhoodSize;
	private final Neighbourhood neighbourhoodType;

	public KohonenLayer(LayerDescriptor layerDescriptor, ILayer previousLayer) {
		super(layerDescriptor, previousLayer);
		this.metricMethod = MetricManager.getMetricFromName(layerDescriptor.getMetric());
		this.neighbourhoodSize = layerDescriptor.getNeighbourhoodSize();
		this.neighbourhoodType = NeighbourhoodManager.getNeighbourhoodFromName(layerDescriptor.getNeighbourhood());
	}

	@Override
	public void compute(Double learningSpeed, Double conscience, Double neighbourhood, List<Double> expectedResults) {

		this.removeNeuronValues();
		List<Double> distances = computeDistances(conscience);
		Integer minIndex = null;
		do {
			for (Neuron neuron : neurons) {
				neuron.addConscience(1.0 / ((double) neurons.size()));
			}
			minIndex = findMinDistanceIndex(distances);
		} while (minIndex == null);
		Neuron lider = neurons.get(minIndex);
		lider.setValue(1.0);
		lider.compute(activationMethod, learningSpeed, 1.0);
		lider.subConscience(conscience);
		for (Point neighbourPos : neighbourhoodType.getPointsFor(lider.getPosition(), sizeDescriptor, neighbourhoodSize)) {
			for (Neuron neighbour : neurons) {
				if (neighbour.getPosition().equals(neighbourPos)) {
					neighbour.compute(activationMethod, learningSpeed, neighbourhood);
				}
			}
		}
	}

	private void removeNeuronValues() {
		for (Neuron neuron : neurons) {
			neuron.setValue(0.0);
		}
	}

	private List<Double> computeDistances(Double conscience) {
		List<Double> distances = new ArrayList<Double>();
		for (Neuron neuron : neurons) {
			Double distance = null;
			if (conscience <= neuron.getConscience()) {
				distance = metricMethod.getValueFor(neuron.calculateDistances());
			}
			distances.add(distance);
		}
		return distances;
	}

	private Integer findMinDistanceIndex(List<Double> distances) {
		Integer minIndex = extractFirstNonNullIndex(distances);
		if (minIndex == null) {
			return null;
		}
		Double minValue = distances.get(minIndex);
		for (int i = 0; i < distances.size(); i++) {
			if (distances.get(i) != null && distances.get(i) < minValue) {
				minValue = distances.get(i);
				minIndex = i;
			}
		}
		return minIndex;
	}

	private Integer extractFirstNonNullIndex(List<Double> distances) {
		for (int i = 0; i < distances.size(); i++) {
			if (distances.get(i) != null) {
				return i;
			}
		}
		return null;
	}
}
